package cuckoo.ioc.factory;

import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import cuckoo.ioc.annotation.Resource;
import cuckoo.log.Logger;
import cuckoo.log.LoggerFactory;
import cuckoo.util.ClassUtil;
import cuckoo.util.StringUtil;

public class DefaultBeanFactory implements BeanFactory {
	private Logger logger = LoggerFactory.getLogger(getClass());

	private Map<String, Class<?>> userClasses = new HashMap<String, Class<?>>();

	private Map<String, Object> independenceBeans = new HashMap<String, Object>();

	private Map<String, Object> userBeans = new HashMap<String, Object>();

	@Override
	public void register(Class<?>... classes) {
		for (Class<?> clazz : classes) {
			String name = getBeanName(clazz);
			if (StringUtil.isBlank(name))
				continue;

			if (userBeans.containsKey(name)) {
				logger.info("already containt class {}", name);
				continue;
			}

			userClasses.put(name, clazz);
		}

		createBeans();
	}

	protected String getBeanName(Class<?> clazz) {
		Annotation[] annotations = clazz.getAnnotations();
		for (Annotation anno : annotations) {
			Class<? extends Annotation> type = anno.annotationType();
			if (type == Resource.class || type.isAnnotationPresent(Resource.class)) {
				String beanName = ClassUtil.invokeMethod(anno, "value");
				return StringUtil.isBlank(beanName) ? clazz.getCanonicalName() : beanName;
			}
		}

		return null;
	}

	private void createBeans() {
		for (String name : userClasses.keySet()) {
			if (userBeans.containsKey(name)) {
				logger.info("already containt class {}", name);
				continue;
			}

			createBean(name);
		}
	}

	private Object createBean(String name) {
		Class<?> clazz = userClasses.get(name);
		if (clazz == null)
			return null;

		Object bean = ClassUtil.instance(clazz);
		if (bean == null)
			return null;

		List<Field> fields = ClassUtil.getFieldsByAnno(clazz, Resource.class);
		for (Field f : fields) {
			String beanName = f.getAnnotation(Resource.class).value();
			if (beanName.startsWith("&")) {
				ClassUtil.setFieldValue(f, bean, independenceBeans.get(beanName));
				continue;
			}

			if (StringUtil.isBlank(beanName)) {
				beanName = findBeanbyType(f.getType());
			}

			if (StringUtil.isBlank(beanName)) {
				continue;
			}

			Object obj = userBeans.get(beanName);

			if (obj == null) {
				if (cycle(name, beanName, new HashSet<String>())) {
					throw new RuntimeException("cycle");
				}

				obj = createBean(beanName);
			}

			if (obj != null)
				ClassUtil.setFieldValue(f, bean, obj);
		}

		bean = postBeanProcessor(bean);
		userBeans.put(name, bean);
		return bean;
	}

	private String findBeanbyType(Class<?> clazz) {
		for (String name : userClasses.keySet()) {
			Class<?> clz = userClasses.get(name);
			if (clz == clazz || clazz.isAssignableFrom(clz)) {
				return name;
			}
		}
		return null;
	}

	private boolean cycle(String name, String fieldName, Set<String> dependent) {
		Class<?> clazz = userClasses.get(fieldName);
		if (clazz == null)
			return false;

		List<Field> fields = ClassUtil.getFieldsByAnno(userClasses.get(fieldName), Resource.class);
		if (fields.isEmpty())
			return false;

		dependent.add(fieldName);

		for (Field f : fields) {
			String type = f.getAnnotation(Resource.class).value();
			if (StringUtil.isBlank(type)) {
				type = findBeanbyType(f.getType());
			}

			if (StringUtil.isBlank(type)) {
				continue;
			}
			if (name.equals(type) || dependent.contains(type) || cycle(name, type, dependent))
				return true;
		}
		return false;
	}

	public void destory() {
		userBeans.clear();
		independenceBeans.clear();
	}

	@Override
	public void registerIndependent(String name, Object obj) {
		if (!independenceBeans.containsKey(name)) {
			independenceBeans.put(name, obj);
		}
	}

	@Override
	@SuppressWarnings("unchecked")
	public <T> T getBean(String name, Class<T> clazz) {
		return (T) getBean(name);
	}

	@Override
	public <T> T getBean(Class<T> clazz) {
		return getBean(getBeanName(clazz), clazz);
	}

	protected Object postBeanProcessor(Object bean) {
		return bean;
	}

	@Override
	public Set<String> getAllBean() {
		return userBeans.keySet();
	}

	@Override
	public Object getBean(String name) {
		if (name.startsWith("&"))
			return independenceBeans.get(name);
		else
			return userBeans.get(name);
	}

}
